// 和后台关联的部分工具
import s94Mjs from "s94-js";
import {_href, _config, _url_api, cache_local} from "@/js/s94_tool.js";
import Spark_md5 from "spark-md5";
import {href_handle, msg, loading} from "@/js/tool.js";
import {use_this_admin} from "@/store/this_admin.js";

export const authorization = {
    authorizationMap: undefined,
    cacheKey: 'authorizationMap',
    init(){
        if (!this.authorizationMap){
            this.authorizationMap = cache_local(this.cacheKey, {})
        }
    },
    urlToHost(url){
        return url.replace(/^(?:.*\/\/)?([\w.]+).*$/, '$1');
    },
    get(url) {
        this.init();
        return this.authorizationMap[this.urlToHost(url)];
    },
    set(url, authorization) {
        this.init();
        let host = this.urlToHost(url);
        if (this.authorizationMap[host] === authorization) return;
        this.authorizationMap[host] = authorization;
        cache_local({[this.cacheKey]: this.authorizationMap});
    }
}

/**统一的http请求封装
 * @param {Object|String} op 配置参数，如果传入字符串，那么看作是url
 * @param {String} op.url 地址
 * @param {String} [op.method] 请求方法，不区分大小写，默认：GET
 * @param {Object|Array} [op.headers] 头信息
 * @param {Number} [op.timeout] 过期时长(毫秒)，默认：30000
 * @param {Boolean} [op.loading] 显示加载中弹窗，默认：false
 * @param {Boolean} [op.async] 异步，默认：true
 * @param {Boolean} [op.cache] 是否启用缓存，存在缓存数据使用缓存数据触发success，然后在异步请求数据，根据真实响应结果和refresh判断是否还需要重复触发success，默认：false
 * @param {Boolean} [op.refresh] 响应数据和缓存数据不同是否需要触发success，默认：true
 * @param {Number} [op.showMessage] 显示消息，建议使用常量ajax.SHOW_MESSAGE_*，默认：ajax.SHOW_MESSAGE_FAIL
 * @param {Function} [ok] 请求成功回调，回调函数的优势：1、可以使用this，2、返回false可以阻止默认的消息提示，3、能处理缓存模式下重复触发的success
 * @param {Function} [fail] 请求失败回调，回调函数的优势：1、可以使用this，2、返回false可以阻止默认的消息提示
 * @returns {Promise<{message:string, data?:*}>}
 */
export function ajax(op, ok, fail){
    //默认配置
    if(typeof(op)==='string') op = {url: op};
    //请求接口处理，在请求url的前面加上代理服务器地址，然后，这样如果设定了
    if (import.meta.env.VITE_PROXY_API_HOST){
        //如果使用了代理服务，请求接口加上代理前缀
        op.url = '/proxy_api'+op.url;
    }else {
        op.url = _url_api(op.url);
    }
    op.headers = op.headers || {};
    op.headers['content-type'] = "application/json";
    op.headers['authorization'] = authorization.get(op.url) || '';
    let needLoading = op.loading;
    if(needLoading) {
        ajax.loadingCount++;
        loading.show();
    }
    let showMessageCode = parseInt(op.showMessage);
    if (isNaN(showMessageCode)) showMessageCode = ajax.SHOW_MESSAGE_FAIL;

    return (new Promise((resolve, reject) => {
        ok = typeof ok === 'function' ? ok : function (){}
        fail = typeof fail === 'function' ? fail : function (){}

        op.success = function(responseData){
            // 更新授权数据
            if (this.response.headers.authorization){
                authorization.set(this.url, this.response.headers.authorization)
            }
            let showMessage = showMessageCode & ajax.SHOW_MESSAGE_OK;
            if(ok.call(this, responseData)!==false && responseData.message && showMessage) msg.ok(responseData.message);
            resolve(responseData);
        }
        op.error = function(responseData){
            this.cleanCache();
            if (typeof responseData == 'string') msg.fail('网络异常，请重试：'+this.response.code);
            if (typeof(ajax.errorCode[this.response.code])=='function' && !this.skip_error_handle) {
                responseData = ajax.errorCode[this.response.code].call(this, responseData);
            }
            let showMessage = showMessageCode & ajax.SHOW_MESSAGE_FAIL;
            if(fail.call(this, responseData)!==false && responseData.message && showMessage) msg.fail(responseData.message);
            reject(ajax.responseError(this.response));
        }
        op.complete = function (response){
            // complete 在 success、error 回调执行后执行
            if(needLoading) ajax.loadingCount--;
            if (ajax.loadingCount <= 0) loading.hide();
        }
        return s94Mjs.ajax(op);
    }))
}
ajax.responseError = (response)=>{
    let message = typeof response.data === 'string' ? `响应数据异常：${response.code}` : (response.data?.message || '未知错误');
    let err = new Error(message);
    err.response = response;
    return err;
}
ajax.SHOW_MESSAGE_NONE = 0;
ajax.SHOW_MESSAGE_FAIL = 1;
ajax.SHOW_MESSAGE_OK = 2;
ajax.SHOW_MESSAGE_ALL = 3;
ajax.loadingCount = 0;

/**ajax请求错误码对应处理
 * @param 401 权限验证异常
 * @param 422 数据验证异常
 */
ajax.errorCode = {
    '401': function (res){ //未登录的处理
        let path = '/user/login';
        if (/\/admin\//.test(this.url)){
            path = '/login';
            use_this_admin().$reset();
        }else {
            path = _config('page.link.user_login_url');
            path = path ? path : '/user/login';
            // 记录一下来源地址，免得微信授权的时候，回退到授权接口
            s94Mjs.cache({'user_login_referer': location.href});
        }
        href_handle(path, 1500);
        return res;
    },
    '402': function (res){ //权限不足的处理
        if (this.skip_402) return res;
        if (this.app){
            setTimeout(function(){
                let iframe_app = this.app.$in_iframe();
                if (iframe_app){
                    iframe_app.__iframe.show = false;
                }else {
                    let href = s94.url(router.resolve('/index/home').href).href;
                    if (window.location.href != href) window.location.href = href;
                }
            },2000)
        }
        return res;
    },
    '405': function (res){ //租户过期的处理
        let path = '/expire';
        href_handle(path, 1500);
        return res;
    },
    '422': function (res){ //参数验证错误的处理
        let errors = [];
        s94Mjs.eachloop(res.errors, '', function ($row){
            errors.push($row);
        })
        res.message = errors.join(' | ');
        return res;
    },
}


/**请求缓存，使用 sign-cache 机制
 * @param {Object|String} op 配置参数，如果传入字符串，那么看作是url
 * @param {String} op.url 地址
 * @param {Object|Array} [op.headers] 头信息
 * @param {Number} [op.timeout] 过期时长(毫秒)，默认：30000
 * @param {Boolean} [op.async] 异步，默认：true
 * @param {Number} [op.showMessage] 显示消息，建议使用常量ajax.SHOW_MESSAGE_*，默认：ajax.SHOW_MESSAGE_ALL
 * @returns {Promise<{message:string, data?:*}>}
 */
export function requestCache(op){
    if (typeof op === 'string') op = {url: op}
    let [path, search] = op.url.split('?');
    // 获取排序后的参数键值对后来确定 cacheKey
    let cacheKey = 'CacheSign:' + path + '?' + Array.from((new URLSearchParams(search)).entries())
        .sort((a, b) => a[0].localeCompare(b[0]))
        .map(([key, value])=>`${key}=${value}`).join('&');

    // 获取缓存的数据和签名
    let cacheData = cache_local(cacheKey, {});
    let {res:cacheRes, sign} = cacheData;
    op.url += `${(~op.url.indexOf('?') ? '&' : '?')}_cache_sign_=${sign||'-'}`;

    return ajax(op, function(res){
        if (res){
            // 获取sign，然后缓存数据
            sign = this.response.headers['cache-sign'];
            cache_local({[cacheKey]: {res,sign}});
        }else {
            console.log(cacheRes, this.url);
        }
    }).then(res=>{
        return res || cacheRes;
    })
}

/** 上传文件，请求会发送：md5，name，filesize，file_category_id，file，start_index以及extend_post的参数
 * @param {File} file 文件对象
 * @param {({type,progress,message})=>{}} [progress] 进度的回调函数，接受一个表示进度的对象(type、progress、message)
 * @param {Object} [extend_post] 额外发送的数据，会覆盖默认值的参数名：name、file_category_id
 * @returns {Promise<unknown>} 返回上传执行后响应的文件数据对象
 */
export function file_upload(file, progress, extend_post){
    let unit_size = 2*1024*1024;// 单次上传最多size，超过的会执行断点续传
    progress = typeof progress=='function' ? progress : function (){}
    let post_data = {
        md5: '',
        name: file.name,
        filesize: file.size,
        file_category_id: 0,
    }
    if (extend_post && typeof extend_post === 'object'){
        post_data = Object.assign(post_data, extend_post);
    }
    post_data.filesize = file.size;

    /**计算文件MD5
     * @param {File} file 文件对象
     * @param {Function} callback 进度回调函数，接受一个参数表示解析进度(0-1)
     * @returns {Promise<unknown>} 传递计算后的MD5
     */
    function file_md5(file, callback){ //得到文件MD5，next传入md5字符串
        var md5 = new Spark_md5();
        let unit_size = 10*1024*1024;
        let fr = new FileReader();

        let over = false, start = 0;
        function loop(){
            let end = 0;
            if(start + unit_size > file.size){
                end = file.size;
                over = true;
            }else {
                end = start + unit_size;
            }
            let data = file.slice(start, end);
            fr.readAsBinaryString(data);
            start = end;
        }
        return new Promise(function (next){
            fr.onload = function (e){
                md5.appendBinary(this.result);
                callback(start/file.size);
                if (over){
                    next(md5.end());
                }else {
                    loop();
                }
            }
            loop();
        })
    }

    /**分片上传文件
     * @param {File} file 上传的文件
     * @param {Number} start 开始上传的未知
     * @param {Function} callback 每次上传的回调函数，接受2个参数(请求响应数据, 上传进度)
     */
    function upload_unit(file, start, callback){
        start = Number(start);
        let end = Math.min(start + unit_size, file.size);
        let over = end >= file.size;
        var fd = new FormData();
        var unit_f = (start==0&&over) ? file : new File([file.slice(start, end)], file.name, {type: file.type});
        fd.append('file', unit_f);
        fd.append('start_index', start);
        for (const k in post_data) {
            fd.append(k, post_data[k]);
        }
        ajax({url:'/home/api/file_upload', data: fd, method:'POST'}, function (res){
            let data = res.data;
            if (data.real_size >= data.size){
                callback(res, {type:'upload', progress:1, message:'上传成功'});
            }else {
                callback(res, {type:'upload', progress:data.real_size/data.size, message:'上传中'});
                setTimeout(function (){
                    upload_unit(file, data.real_size, callback);
                },5);
            }
            return false;
        }, function (res){
            callback(res, {type:'upload_error', progress:1, message:'上传文件出错'});
            return false;
        })
    }

    //第一步，计算md5
    return file_md5(file, function (p){
        progress({type:'md5', progress:p, message:'解析中'});
    }).then(function (md5){
        post_data.md5 = md5;
        return new Promise(function (over, reject){
            upload_unit(file, 0, function (res, p){
                if (p.type == 'upload_error'){
                    reject(new Error(res.message || p.message));
                }else {
                    progress(p);
                    if (p.progress >= 1) over(res.data);
                }
            })
        })
    });
}
